<template>
    <view class="uni-forms">
        <form>
            <slot></slot>
        </form>
    </view>
</template>

<script>
import Validator from './validate.js';
import {
    deepCopy,
    getValue,
    isRequiredField,
    setDataValue,
    getDataValue,
    realName,
    isRealName,
    rawData,
    isEqual
} from './utils.js'

// #ifndef VUE3
// 后续会慢慢废弃这个方法
import Vue from 'vue';
Vue.prototype.binddata = function (name, value, formName) {
    if (formName) {
        this.$refs[formName].setValue(name, value);
    } else {
        let formVm;
        for (let i in this.$refs) {
            const vm = this.$refs[i];
            if (vm && vm.$options && vm.$options.name === 'uniForms') {
                formVm = vm;
                break;
            }
        }
        if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
        formVm.setValue(name, value);
    }
};
// #endif
/**
 * Forms 表单
 * @description 由输入框、选择器、单选框、多选框等控件组成，用以收集、校验、提交数据
 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
 * @property {Object} rules	表单校验规则
 * @property {String} validateTrigger = [bind|submit|blur]	校验触发器方式 默认 submit
 * @value bind		发生变化时触发
 * @value submit	提交时触发
 * @value blur	  失去焦点时触发
 * @property {String} labelPosition = [top|left]	label 位置 默认 left
 * @value top		顶部显示 label
 * @value left	左侧显示 label
 * @property {String} labelWidth	label 宽度，默认 70px
 * @property {String} labelAlign = [left|center|right]	label 居中方式  默认 left
 * @value left		label 左侧显示
 * @value center	label 居中
 * @value right		label 右侧对齐
 * @property {String} errShowType = [undertext|toast|modal]	校验错误信息提示方式
 * @value undertext	错误信息在底部显示
 * @value toast			错误信息toast显示
 * @value modal			错误信息modal显示
 * @event {Function} submit	提交时触发
 * @event {Function} validate	校验结果发生变化触发
 */
export default {
    name: 'uniForms',
    emits: ['validate', 'submit'],
    options: {
        virtualHost: true
    },
    props: {
        // 即将弃用
        value: {
            type: Object,
            default() {
                return null;
            }
        },
        // vue3 替换 value 属性
        modelValue: {
            type: Object,
            default() {
                return null;
            }
        },
        // 1.4.0 开始将不支持 v-model ，且废弃 value 和 modelValue
        model: {
            type: Object,
            default() {
                return null;
            }
        },
        // 表单校验规则
        rules: {
            type: Object,
            default() {
                return {};
            }
        },
        //校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal]
        errShowType: {
            type: String,
            default: 'undertext'
        },
        // 校验触发器方式 默认 bind 取值 [bind|submit]
        validateTrigger: {
            type: String,
            default: 'submit'
        },
        // label 位置，默认 left 取值  top/left
        labelPosition: {
            type: String,
            default: 'left'
        },
        // label 宽度
        labelWidth: {
            type: [String, Number],
            default: ''
        },
        // label 居中方式，默认 left 取值 left/center/right
        labelAlign: {
            type: String,
            default: 'left'
        },
        border: {
            type: Boolean,
            default: false
        }
    },
    provide() {
        return {
            uniForm: this
        }
    },
    data() {
        return {
            // 表单本地值的记录，不应该与传如的值进行关联
            formData: {},
            formRules: {}
        };
    },
    computed: {
        // 计算数据源变化的
        localData() {
            const localVal = this.model || this.modelValue || this.value
            if (localVal) {
                return deepCopy(localVal)
            }
            return {}
        }
    },
    watch: {
        // 监听数据变化 ,暂时不使用，需要单独赋值
        // localData: {},
        // 监听规则变化
        rules: {
            handler: function (val, oldVal) {
                this.setRules(val)
            },
            deep: true,
            immediate: true
        }
    },
    created() {
        // #ifdef VUE3
        let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
        if (!getbinddata) {
            getApp().$vm.$.appContext.config.globalProperties.binddata = function (name, value, formName) {
                if (formName) {
                    this.$refs[formName].setValue(name, value);
                } else {
                    let formVm;
                    for (let i in this.$refs) {
                        const vm = this.$refs[i];
                        if (vm && vm.$options && vm.$options.name === 'uniForms') {
                            formVm = vm;
                            break;
                        }
                    }
                    if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
                    formVm.setValue(name, value);
                }
            }
        }
        // #endif

        // 子组件实例数组
        this.childrens = []
        // TODO 兼容旧版 uni-data-picker ,新版本中无效，只是避免报错
        this.inputChildrens = []
        this.setRules(this.rules)
    },
    methods: {
        /**
         * 外部调用方法
         * 设置规则 ，主要用于小程序自定义检验规则
         * @param {Array} rules 规则源数据
         */
        setRules(rules) {
            // TODO 有可能子组件合并规则的时机比这个要早，所以需要合并对象 ，而不是直接赋值，可能会被覆盖
            this.formRules = Object.assign({}, this.formRules, rules)
            // 初始化校验函数
            this.validator = new Validator(rules);
        },

        /**
         * 外部调用方法
         * 设置数据，用于设置表单数据，公开给用户使用 ， 不支持在动态表单中使用
         * @param {Object} key
         * @param {Object} value
         */
        setValue(key, value) {
            let example = this.childrens.find(child => child.name === key);
            if (!example) return null;
            this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || [])
            return example.onFieldChange(this.formData[key]);
        },

        /**
         * 外部调用方法
         * 手动提交校验表单
         * 对整个表单进行校验的方法，参数为一个回调函数。
         * @param {Array} keepitem 保留不参与校验的字段
         * @param {type} callback 方法回调
         */
        validate(keepitem, callback) {
            return this.checkAll(this.formData, keepitem, callback);
        },

        /**
         * 外部调用方法
         * 部分表单校验
         * @param {Array|String} props 需要校验的字段
         * @param {Function} 回调函数
         */
        validateField(props = [], callback) {
            props = [].concat(props);
            let invalidFields = {};
            this.childrens.forEach(item => {
                const name = realName(item.name)
                if (props.indexOf(name) !== -1) {
                    invalidFields = Object.assign({}, invalidFields, {
                        [name]: this.formData[name]
                    });
                }
            });
            return this.checkAll(invalidFields, [], callback);
        },

        /**
         * 外部调用方法
         * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组，如不传则移除整个表单的校验结果
         * @param {Array|String} props 需要移除校验的字段 ，不填为所有
         */
        clearValidate(props = []) {
            props = [].concat(props);
            this.childrens.forEach(item => {
                if (props.length === 0) {
                    item.errMsg = '';
                } else {
                    const name = realName(item.name)
                    if (props.indexOf(name) !== -1) {
                        item.errMsg = '';
                    }
                }
            });
        },

        /**
         * 外部调用方法 ，即将废弃
         * 手动提交校验表单
         * 对整个表单进行校验的方法，参数为一个回调函数。
         * @param {Array} keepitem 保留不参与校验的字段
         * @param {type} callback 方法回调
         */
        submit(keepitem, callback, type) {
            for (let i in this.dataValue) {
                const itemData = this.childrens.find(v => v.name === i);
                if (itemData) {
                    if (this.formData[i] === undefined) {
                        this.formData[i] = this._getValue(i, this.dataValue[i]);
                    }
                }
            }

            if (!type) {
                console.warn('submit 方法即将废弃，请使用validate方法代替！');
            }

            return this.checkAll(this.formData, keepitem, callback, 'submit');
        },

        // 校验所有
        async checkAll(invalidFields, keepitem, callback, type) {
            // 不存在校验规则 ，则停止校验流程
            if (!this.validator) return
            let childrens = []
            // 处理参与校验的item实例
            for (let i in invalidFields) {
                const item = this.childrens.find(v => realName(v.name) === i)
                if (item) {
                    childrens.push(item)
                }
            }

            // 如果validate第一个参数是funciont ,那就走回调
            if (!callback && typeof keepitem === 'function') {
                callback = keepitem;
            }

            let promise;
            // 如果不存在回调，那么使用 Promise 方式返回
            if (!callback && typeof callback !== 'function' && Promise) {
                promise = new Promise((resolve, reject) => {
                    callback = function (valid, invalidFields) {
                        !valid ? resolve(invalidFields) : reject(valid);
                    };
                });
            }

            let results = [];
            // 避免引用错乱 ，建议拷贝对象处理
            let tempFormData = JSON.parse(JSON.stringify(invalidFields))
            // 所有子组件参与校验,使用 for 可以使用  awiat
            for (let i in childrens) {
                const child = childrens[i]
                let name = realName(child.name);
                const result = await child.onFieldChange(tempFormData[name]);
                if (result) {
                    results.push(result);
                    // toast ,modal 只需要执行第一次就可以
                    if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
                }
            }


            if (Array.isArray(results)) {
                if (results.length === 0) results = null;
            }
            if (Array.isArray(keepitem)) {
                keepitem.forEach(v => {
                    let vName = realName(v);
                    let value = getDataValue(v, this.localData)
                    if (value !== undefined) {
                        tempFormData[vName] = value
                    }
                });
            }

            // TODO submit 即将废弃
            if (type === 'submit') {
                this.$emit('submit', {
                    detail: {
                        value: tempFormData,
                        errors: results
                    }
                });
            } else {
                this.$emit('validate', results);
            }

            // const resetFormData = rawData(tempFormData, this.localData, this.name)
            let resetFormData = {}
            resetFormData = rawData(tempFormData, this.name)
            callback && typeof callback === 'function' && callback(results, resetFormData);

            if (promise && callback) {
                return promise;
            } else {
                return null;
            }

        },

        /**
         * 返回validate事件
         * @param {Object} result
         */
        validateCheck(result) {
            this.$emit('validate', result);
        },
        _getValue: getValue,
        _isRequiredField: isRequiredField,
        _setDataValue: setDataValue,
        _getDataValue: getDataValue,
        _realName: realName,
        _isRealName: isRealName,
        _isEqual: isEqual
    }
};
</script>

<style lang="scss">
.uni-forms {}
</style>
